import java.awt.Point;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.locks.ReentrantLock;

public class Simulation
{
	private class SimulationTickData
	{
		private int TickCount;

		public SimulationTickData()
		{
			TickCount = 0;
		}
	}
	
	private SimulationTickData SimulationTickData;

	private class SimulationTickThread extends Thread
	{
		@Override
		public void run()
		{
			// long StartTime = System.currentTimeMillis();
			SimulationTick();
			// System.out.println(System.currentTimeMillis() - StartTime);
		}
	}

	private final int SimulationIOProgramLineNumberMax = 1000000;
	
	private int SlowDownDroppedTicksDesired = 0;
	private int SlowDownDroppedTicksCurrent = 0;

	private final static int TicksPerSecond = 30; // don't use too few or too much delay when passing Line chains, do not use too much or too high CPU load
	private final static int TicksPerIOTextAreaLine = 6; // the more, the slower the ILines are worked up

	public static int GetTicksPerSecond()
	{
		return TicksPerSecond;
	}

	public static int TicksPerIOTextAreaLine()
	{
		return TicksPerIOTextAreaLine;
	}

	private MainWindow.GUIElements GUIElements;
	private IOProgram IOProgram;
	private ObjectStorage ObjectStorage;
	private Ticker Ticker;

	private Timer TickTimer = null; // while (true)-loop did not work (locks GUI)
	private boolean TickTimerRunning = false; // if running, set to false to end simulation
	private boolean TickTimerPaused = false;
	private TimerTask TickTimerTask;
	private ReentrantLock TickTimerMutex = new ReentrantLock(); // see code...
	private int TickTimerEventsLostCount = 0;

	public Simulation(MainWindow.GUIElements GUIElements, IOProgram IOProgram, Ticker Ticker, ObjectStorage ObjectStorage)
	{
		this.GUIElements = GUIElements;
		this.IOProgram = IOProgram;
		this.ObjectStorage = ObjectStorage;
		this.Ticker = Ticker;
	}

	public boolean IsSimulationPaused()
	{
		return TickTimerPaused;
	}

	public boolean IsSimulationRunning()
	{
		return TickTimerRunning;
	}

	public void PauseSimulation()
	{
		TickTimerMutex.lock();
		if (TickTimerRunning)
		{
			TickTimerPaused = true;
		}
		TickTimerMutex.unlock();
	}

	private void ProcessActivatedObjects()
	{ // apply additional actions to (selected) activated DrawObjects after each simulation tick

		String TextFieldTextLast = "";

		ArrayList<DrawObject> ActivatedTextFields = ObjectStorage.GetActivatedObjects(new TextField(new Point(0, 0)));
		for (Iterator<DrawObject> i = ActivatedTextFields.iterator(); i.hasNext();)
		{
			DrawObject o = i.next();
			if (o.IsTextField()) // additional check (actually surplus, but just to make sure), ObjectStorage.GetActivatedObjects([...]) should have returned TextFields only
			{
				IOProgram.ReactOnTextFieldActivation((TextField) o);

				String TextFieldText = ((TextField) o).GetText();

				if (TextFieldText.indexOf("=") >= 0)
				{
					ObjectStorage.UpdateHormoneStrengthForAll(Neuron.ExtractHormoneName(TextFieldText), Neuron.ExtractHormoneStrength(TextFieldText));
				}

				if (!(TextFieldText.equals(TextFieldTextLast)))
				{
					// NOTE (2022-08-13_14-33): Ticker is currently disabled as turned out
					// to be rather useless for understanding (rather confusing), enable
					// just the following code line to enable it again, if desired:

					// Ticker.ReceiveString(TextFieldText + "... ");
				}
				TextFieldTextLast = TextFieldText;
			}
		}
	}

	public void ResumeSimulation()
	{
		TickTimerMutex.lock();
		if (TickTimerRunning)
		{
			TickTimerPaused = false;
		}
		TickTimerMutex.unlock();
	}

	private void SimulationTick()
	{
		// verify
		if (TickTimerRunning == false) // maybe StopSimulation() called
		{
			// DO NOT lock TickTimerMutex here or deadlock (UserActionManager locked it via PauseSimulation() and then TickTimer event calls this here...)
			return;
		}
		if (TickTimerPaused)
		{
			// DO NOT lock TickTimerMutex here or deadlock
			return;
		}

		if (SlowDownDroppedTicksDesired > 0)
		{
			if (SlowDownDroppedTicksCurrent >= SlowDownDroppedTicksDesired)
			{
				SlowDownDroppedTicksCurrent = 0;
			}
			else
			{
				SlowDownDroppedTicksCurrent++;
				return; // just ignore Timer tick
			}
		}

		// begin
		if (TickTimerMutex.tryLock() == false)
		{
			// don't accumulate waiting ticks (happened in tests), we don't lose anything if returning here
			TickTimerEventsLostCount++;
			if (TickTimerEventsLostCount > 2000000000)
				TickTimerEventsLostCount = 2000000000; // just to make sure, counter just stops, user will conclude anyway there were MANY ticks lost/delayed ;-P
			IOProgram.SetTickLostCount(TickTimerEventsLostCount);
			return;
		}

		GUIElements.GetMainWindow().DisableRedraw(); // redrawn when enabled again, if some dirty flag set
		
		ObjectStorage.ResetUpdateHormoneStrengthForAll();

		ObjectStorage.ExciteLines();

		ObjectStorage.TransferActivatedTextFieldExcitement();

		ObjectStorage.AfterCollectingAllExcitement();

		int IOProgramLineExecutedCurrent;

		while (true) // "continue;" if ILine is a comment, else "break;" at end
		{
			IOProgramLineExecutedCurrent = (int) Math.min((long) SimulationIOProgramLineNumberMax, SimulationTickData.TickCount / TicksPerIOTextAreaLine);

			IOProgram.SetIOProgramLineExecutedCurrent(IOProgramLineExecutedCurrent);

			if ((SimulationTickData.TickCount % TicksPerIOTextAreaLine) == 0) // next ILine to be processed?
			{
				String ILine = IOProgram.GetILine(IOProgramLineExecutedCurrent);

				if (ILine == null) // all ILines worked up (and no REPEAT)?
				{
					break;
				}

				if (ILine.startsWith("// ")) // comment?
				{
					SimulationTickData.TickCount += (1 * TicksPerIOTextAreaLine);
					continue; // check next line instantly (in while (true)-loop)
				}
			}
			break; // "continue;" if ILine is a comment
		}

		ProcessActivatedObjects();

		IOProgram.DoRedraw();

		ObjectStorage.LoseExcitementForAllObjects();

		if ((SimulationTickData.TickCount % TicksPerIOTextAreaLine) == 0) // next ILine to be processed?
		{
			String ILine = IOProgram.GetILine(IOProgramLineExecutedCurrent);

			if (ILine == null) // all ILines worked up (and no REPEAT)?
			{
				GUIElements.GetMainWindow().EnableRedraw();
				// StopTickTimer(); // do not stop timer, or MainWindow title gets screwed up and the user could not do manual activation any more
				TickTimerMutex.unlock();
				return;
			}

			switch (ILine)
			{
			case "REPEAT":
				SimulationTickData.TickCount = 0;
				IOProgram.ClearOLines();
				GUIElements.GetMainWindow().EnableRedraw();
				TickTimerMutex.unlock();
				return;
			}

			if (ILine.indexOf(IOProgram.MANUAL_ACTIVATION) == 0)
			{
				SimulationTickData.TickCount = 0;
				// keep OLines, no need to delete them, user might want to view, we don't alter them now anyway
				GUIElements.GetMainWindow().EnableRedraw();
				TickTimerMutex.unlock();
				return;
			}

			if (ILine.indexOf("GOTO=") == 0)
			{
				String GotoTarget = ILine.substring("GOTO=".length());
				for (int i = 0; i < SimulationIOProgramLineNumberMax; i++)
				{
					if (IOProgram.GetILine(i) == null)
						break;
					if (IOProgram.GetILine(i).equals(GotoTarget))
					{
						SimulationTickData.TickCount = (i * TicksPerIOTextAreaLine);
						// keep OLines (currently, to be tried out...)
						GUIElements.GetMainWindow().EnableRedraw();
						TickTimerMutex.unlock();
						return;
					}
				}
			}

			if (ILine.indexOf("DROPPED_TICKS=") == 0)
			{
				String DroppedTicksString = ILine.substring("DROPPED_TICKS=".length());
				try
				{
					SlowDownDroppedTicksDesired = Integer.parseInt(DroppedTicksString);
					if (SlowDownDroppedTicksDesired < 0)
						SlowDownDroppedTicksDesired = 0;
					if (SlowDownDroppedTicksDesired > 10)
						SlowDownDroppedTicksDesired = 10;

					SlowDownDroppedTicksCurrent = 0;
				}
				catch (Exception e)
				{
					// ignore wrong number format
				}
			}

			if (ILine.indexOf("=") >= 0)
			{
				ObjectStorage.UpdateHormoneStrengthForAll(Neuron.ExtractHormoneName(ILine), Neuron.ExtractHormoneStrength(ILine));
			}

			String[] ILineParts = ("/" + ILine + "/").split("/");
			for (int p = 0; p < ILineParts.length; p++)
			{
				if (ILineParts[p].length() >= 1)
				{
					ObjectStorage.ExciteTextFields(ILineParts[p]);
				}
			}
		}

		SimulationTickData.TickCount++; // do at very end, code relies on that

		GUIElements.GetMainWindow().EnableRedraw();
		
		TickTimerMutex.unlock();
		return;
	}

	public void StartSimulation()
	{
		// NOTE: we set GUIElements' state per method calls to make sure they
		// are in sync with drawn stuff (don't use multi-threading)!

		SimulationTickData = new SimulationTickData();

		Ticker.Clear();

		SlowDownDroppedTicksDesired = 0;
		SlowDownDroppedTicksCurrent = 0;
		
		TickTimerMutex = new ReentrantLock();
		TickTimerTask = new TimerTask()
		{
			@Override
			public void run()
			{
				(new SimulationTickThread()).start();
			}
		};

		TickTimer = new Timer();
		TickTimerRunning = true;
		TickTimerEventsLostCount = 0;
		TickTimer.schedule(TickTimerTask, 0, (1000 / TicksPerSecond));
	}

	public void StopSimulation()
	{
		StopTickTimer(); // SimulationTick() must react to this
	}

	private void StopTickTimer()
	{
		TickTimerMutex.lock();
		if (TickTimerRunning)
		{
			TickTimer.cancel();
			TickTimer.purge(); // allows creating a new TimerTask, else exception (tested)
			TickTimerRunning = false;
		}
		TickTimerMutex.unlock();
	}
}
